package simpletestbed.core;


import java.io.DataInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;

import java.io.DataOutputStream;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;

import simpletestbed.utils.DateUtils;
import simpletestbed.utils.MultiOutputStream;

public class SimpleTestBedServer {
	
	public final static int TCP_PORT = 8888;
	
	private ServerSocket socketServer = null;
	
	private boolean bRunning = false;
	
	private final static long VERSION_NUMBER = 201103221236L;
	
	
	public final static String SENDCLASS_COMMAND = "sendclass";
	public final static String RUN_COMMAND = "run";
	
	public SimpleTestBedServer() throws IOException, UnknownHostException {
		MultiOutputStream mos = new MultiOutputStream(System.out);
		FileOutputStream serverLogFos = new FileOutputStream(getHostname()+"_simpletestbed.log", true);
		mos.addOutputStream(serverLogFos, true);
		System.setOut(new PrintStream(mos, true));
	}
	
	public void start() throws IOException {
		this.socketServer = new ServerSocket(TCP_PORT);
		this.bRunning = true;
		System.out.println(DateUtils.now()+" SimpleTestBed ("+VERSION_NUMBER+") server started...");
		System.out.println(DateUtils.now() + " Waiting for a client...");
		while(bRunning) {
			Socket clientSocket = socketServer.accept();
			System.out.println(DateUtils.now() + " Accepted a client from "
					+ clientSocket.getInetAddress());
			DataInputStream dis = new DataInputStream(clientSocket.getInputStream());
			DataOutputStream dos = new DataOutputStream(clientSocket.getOutputStream());
			String command = dis.readUTF();
			Boolean isSync = dis.readBoolean();
			if (isSync) {
				try {
					execCommand(command, dis, dos);
				}catch(Exception e) {
					e.printStackTrace();
				}
				System.out.println(DateUtils.now() + " Waiting for a client...");
			}
			else {
				CommandHandlerThread cmdHandlerThread = new CommandHandlerThread(command, dis, dos);
				cmdHandlerThread.start();
			}
		}
	}
	
	private static void execCommand(String cmd, DataInputStream dis, DataOutputStream dos) throws Exception {
        if (cmd.equals(RUN_COMMAND)) {
        	execRun(dis, dos);
        }
        else if (cmd.equals(SENDCLASS_COMMAND)) {
        	execSendClasses(dis, dos);
        }
        else {
        	throw new Error("Unknwon command : "+cmd);
        }
        System.out.println(DateUtils.now() + " Waiting for a client...");
	}
	
	public static String getHostname() throws UnknownHostException {
		return JVMHelper.getHostname();
	}
	
	private static void execSendClasses(DataInputStream dis, DataOutputStream dos) throws Exception {
		System.out.println(DateUtils.now()+" Command: "+SENDCLASS_COMMAND+" starts ..............");
		
		// Create the folder to store the client java classes
		String clientClassFolderName = dis.readUTF();
		File clientClassFolder = new File(clientClassFolderName);
		if (clientClassFolder.exists() == false) {
			System.out.println(DateUtils.now()+" Create client class folder : "+clientClassFolderName);
			clientClassFolder.mkdir();
		}
		
		// Read the client's java code package and write it into the local folder
		JavaClassPackage classPackage = new JavaClassPackage();
		System.out.println(DateUtils.now()+" Start to write client java classes ");
		classPackage.writeToLocal(dis, clientClassFolderName);
		System.out.println(DateUtils.now()+" End writing client java classes ");
		
		// Add client class path into JVM
		JVMHelper.addClassPath(clientClassFolderName);
		System.out.println(DateUtils.now()+" Added "+clientClassFolderName+" into JVM classpath");
		
		System.out.println(DateUtils.now()+" Command: "+SENDCLASS_COMMAND+" finishes");
	}
	
	private static void execRun(DataInputStream dis, DataOutputStream dos) throws Exception {
		System.out.println(DateUtils.now()+" Command: "+RUN_COMMAND+" starts ..............");
		
		// Read the start class name and method name
		String startClassName = dis.readUTF();
		System.out.println(DateUtils.now()+" ServerCaller class name :"+startClassName);
		
		String startMethodName = dis.readUTF();
		System.out.println(DateUtils.now()+" ServerCaller method name :"+startMethodName);
		
		boolean checkMemUsage = dis.readBoolean();
		System.out.println(DateUtils.now()+" ServerCaller checkMemUsage  :"+checkMemUsage);
		
		long timeout = dis.readLong();
		System.out.println(DateUtils.now()+" ServerCaller timeout :"+timeout);
		
		int argLength = dis.readInt();
		byte[] startArg = new byte[argLength];
		dis.readFully(startArg);
		
		System.out.println(DateUtils.now()+" ServerCaller start: "+startClassName+", method : "
				+startMethodName);
		
		// Start the client's method
		Object targetObject = Class.forName(startClassName).newInstance();
		Method method = Class.forName(startClassName).getMethod(startMethodName, 
				new Class[]{byte[].class, boolean.class, long.class});
		byte[] result = (byte[])method.invoke(targetObject, new Object[]{startArg, checkMemUsage, timeout});
		System.out.println(DateUtils.now()+" ServerCaller finished!");
		if (result != null) {
			// Send back the result
			dos.writeInt(result.length);
			dos.write(result);
		}
		System.out.println(DateUtils.now()+" Command: "+RUN_COMMAND+" finishes");
	}
	
	
	/**
	 * Command handler thread
	 * @author ltang002
	 *
	 */
	static class CommandHandlerThread extends Thread {
		
		private String command = null;
		
		private DataInputStream dis = null;
		
		private DataOutputStream dos = null;
		
		public CommandHandlerThread(String cmd, DataInputStream dis, DataOutputStream dos) {
			this.command = cmd;
			this.dis = dis;
			this.dos = dos;
		}
		
		@Override
		public void run()  {
			try {
				SimpleTestBedServer.execCommand(this.command, dis, dos);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}
	}
	

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		try {
			SimpleTestBedServer server = new SimpleTestBedServer();
			server.start();
		}catch(Exception e) {
			e.printStackTrace();
		}
	}

}
